/**
 *
 ******************************************************************************
 * @作  者  Musk Han@XTARK
 * @内  容  OLED显示驱动程序
 *
 ******************************************************************************
 */

#include "ax_oled_1305.h"

#include "ax_oled_ascii.h"
#include "bsp_def.h"

// OLED信号定义
static SPI_HandleTypeDef* hspi_ptr = BSP_OLED_SPI;

#define OLED_DC_L() HAL_GPIO_WritePin(BSP_OLED_DC_GPIO, BSP_OLED_DC_PIN, GPIO_PIN_RESET)  // DC
#define OLED_DC_H() HAL_GPIO_WritePin(BSP_OLED_DC_GPIO, BSP_OLED_DC_PIN, GPIO_PIN_SET)

#define OLED_RST_L() HAL_GPIO_WritePin(BSP_OLED_RESET_GPIO, BSP_OLED_RESET_PIN, GPIO_PIN_RESET)  // RES
#define OLED_RST_H() HAL_GPIO_WritePin(BSP_OLED_RESET_GPIO, BSP_OLED_RESET_PIN, GPIO_PIN_SET)

// 函数定义
static uint8_t SPI1_Send_Byte(uint8_t dat);
static void OLED_WriteCmd(uint8_t data);
static void OLED_WriteData(uint8_t data);
static void OLED_Set_Pos(uint8_t x, uint8_t y);

/**
 * @简  述  OLED初始化
 * @参  数  无
 * @返回值  无
 */
void AX_OLED_Init(void) {
    //__HAL_SPI_ENABLE(&hspi1);//已经自动开启

    // 启动传输
    SPI1_Send_Byte(0xff);

    OLED_RST_H();
    HAL_Delay(100);
    OLED_RST_L();
    HAL_Delay(200);
    OLED_RST_H();

    OLED_WriteCmd(0xAE);  //--turn off oled panel
    OLED_WriteCmd(0x00);  //---set low column address
    OLED_WriteCmd(0x10);  //---set high column address
    OLED_WriteCmd(0x40);  //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_WriteCmd(0x81);  //--set contrast control register
    OLED_WriteCmd(0xEF);  // Set SEG Output Current Brightness
    OLED_WriteCmd(0xA1);  //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_WriteCmd(0xC8);  // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_WriteCmd(0xA6);  //--set normal display
    OLED_WriteCmd(0xA8);  //--set multiplex ratio(1 to 64)
    OLED_WriteCmd(0x3f);  //--1/64 duty
    OLED_WriteCmd(0xD3);  //-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
    OLED_WriteCmd(0x00);  //-not offset
    OLED_WriteCmd(0xd5);  //--set display clock divide ratio/oscillator frequency
    OLED_WriteCmd(0x80);  //--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_WriteCmd(0xD9);  //--set pre-charge period
    OLED_WriteCmd(0xF1);  // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_WriteCmd(0xDA);  //--set com pins hardware configuration
    OLED_WriteCmd(0x12);
    OLED_WriteCmd(0xDB);  //--set vcomh
    OLED_WriteCmd(0x40);  // Set VCOM Deselect Level
    OLED_WriteCmd(0x20);  //-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_WriteCmd(0x02);  //
    OLED_WriteCmd(0x8D);  //--set Charge Pump enable/disable
    OLED_WriteCmd(0x14);  //--set(0x10) disable
    OLED_WriteCmd(0xA4);  // Disable Entire Display On (0xa4/0xa5)
    OLED_WriteCmd(0xA6);  // Disable Inverse Display On (0xa6/a7)
    OLED_WriteCmd(0xAF);  //--turn on oled panel

    AX_OLED_ClearScreen();
}

/**
 * @简  述  OLED屏幕清除
 * @参  数  无
 * @返回值	 无
 */
void AX_OLED_ClearScreen(void) {
    uint8_t i, n;
    for (i = 0; i < 8; i++) {
        OLED_WriteCmd(0xb0 + i);  // 设置页地址（0~7）
        OLED_WriteCmd(0x00);      // 设置显示位置—列低地址
        OLED_WriteCmd(0x10);      // 设置显示位置—列高地址
        for (n = 0; n < 128; n++)
            OLED_WriteData(0);
    }  // 更新显示
}

/**
 * @简  述  OLED指定位置显示一个ASCII字符（6X8）
 * @参  数  x：横坐标 0~123，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：显示字符
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispChar(uint8_t x, uint8_t y, uint8_t ch, uint8_t mode) {
    uint8_t c = 0, i = 0;

    c = ch - ' ';  // 得到偏移后的值

    // 显示范围控制，超出部分不显示
    if ((x < 123) && (y < 8)) {
        if (mode != 0) {
            OLED_Set_Pos(x, y);
            for (i = 0; i < 6; i++)
                OLED_WriteData(~ASCII6X8[c * 6 + i]);
        } else {
            OLED_Set_Pos(x, y);
            for (i = 0; i < 6; i++)
                OLED_WriteData(ASCII6X8[c * 6 + i]);
        }
    }
}

/**
 * @简  述  OLED指定位置显示ASCII字符串（8X16）,自动换行。
 * @参  数  x：横坐标 0~123，超出范围不显示
 *          y：纵坐标 0~7，超出范围不显示
 *          ch：字符串指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispStr(uint8_t x, uint8_t y, uint8_t* ch, uint8_t mode) {
    uint8_t j = 0;

    if ((x < 123) && (y < 8)) {
        while (ch[j] != '\0') {
            AX_OLED_DispChar(x, y, ch[j], mode);
            x += 6;

            if (x > 122) {
                x = 0;
                y += 1;  // 当y>6时，超出部分不再显示
            }
            j++;
        }
    }
}
/**
 * @简  述  OLED指定位置显示一个数字（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          num：显示的数字
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispNum(uint8_t x, uint8_t y, uint8_t num, uint8_t mode)  // 显示单个数字
{
    if (num < 10) {
        AX_OLED_DispChar(x, y, ('0' + num), mode);
    }
}

/**
 * @简  述  OLED指定位置显示数值（可显示负数）
 * @参  数  x：横坐标 0~122，超出范围不显示
 *          y：纵坐标 0~7，超出范围不显示
 *          value：显示的数值，32位整型数据，最大显示10位（inte + deci < 10）
 *          inte：整数位数，范围（>0, inte + deci < 10）
 *          deci：小数位数, 范围（inte + deci < 10）（取0时，为整数）
 *          mode：显示模式，0-正常显示，1-反白显示
 * @说  明  输入value为整型，如果是小数，可扩大相应倍数实现。例如123.45,先乘100，显示时整数位取3，小数位取2。
 *          如果显示数据可能为负数时，整数位数需要比最大位数多一位。例如-122，整数位取4。
 * @返回值
 */
void AX_OLED_DispValue(uint8_t x, uint8_t y, int32_t value, uint8_t inte, uint8_t deci, uint8_t mode) {
    uint8_t dis_number[10] = {0};
    uint8_t i;
    uint32_t temp;

    if ((inte > 0) && ((inte + deci) < 10)) {
        if (value < 0)
            temp = (0 - value);
        else
            temp = value;

        dis_number[1] = temp % 10;
        dis_number[2] = (temp % 100) / 10;
        dis_number[3] = (temp % 1000) / 100;
        dis_number[4] = (temp % 10000) / 1000;
        dis_number[5] = (temp % 100000) / 10000;
        dis_number[6] = (temp % 1000000) / 100000;
        dis_number[7] = (temp % 10000000) / 1000000;
        dis_number[8] = (temp % 100000000) / 10000000;
        dis_number[9] = (temp % 1000000000) / 100000000;

        if (value < 0) {
            AX_OLED_DispChar(x, y, '-', 0);

            for (i = 0; i < (inte - 1); i++) {
                AX_OLED_DispNum(x + 6 + (i * 6), y, dis_number[inte + deci - i - 1], mode);
            }
            if (deci != 0) {
                AX_OLED_DispChar(x + (inte * 6), y, '.', mode);

                for (i = 0; i < deci; i++) {
                    AX_OLED_DispNum(x + (inte * 6) + 6 + (i * 6), y, dis_number[deci - i], mode);
                }
            }
        } else {
            for (i = 0; i < inte; i++) {
                AX_OLED_DispNum(x + (i * 6), y, dis_number[inte + deci - i], mode);
            }
            if (deci != 0) {
                AX_OLED_DispChar(x + (inte * 6), y, '.', mode);

                for (i = 0; i < deci; i++) {
                    AX_OLED_DispNum(x + (inte * 6) + 6 + (i * 6), y, dis_number[deci - i], mode);
                }
            }
        }
    } else  // 参数错误
    {
        // AX_OLED_DisplayChar(x, y, 'X', 0);
    }
}

/**
 * @简  述  OLED指定位置显示一个ASCII字符（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：显示字符
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Char(uint8_t x, uint8_t y, uint8_t ch, uint8_t mode) {
    uint8_t c = 0, i = 0;

    c = ch - ' ';  // 得到偏移后的值

    // 显示范围控制，超出部分不显示
    if ((x < 121) && (y < 7)) {
        if (mode != 0) {
            OLED_Set_Pos(x, y);
            for (i = 0; i < 8; i++)
                OLED_WriteData(~ASCII8X16[c * 16 + i]);
            OLED_Set_Pos(x, y + 1);
            for (i = 0; i < 8; i++)
                OLED_WriteData(~ASCII8X16[c * 16 + i + 8]);
        } else {
            OLED_Set_Pos(x, y);
            for (i = 0; i < 8; i++)
                OLED_WriteData(ASCII8X16[c * 16 + i]);
            OLED_Set_Pos(x, y + 1);
            for (i = 0; i < 8; i++)
                OLED_WriteData(ASCII8X16[c * 16 + i + 8]);
        }
    }
}

/**
 * @简  述  OLED指定位置显示ASCII字符串（8X16）,自动换行。
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：字符串指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Str(uint8_t x, uint8_t y, uint8_t* ch, uint8_t mode) {
    uint8_t j = 0;

    if ((x < 121) && (y < 7)) {
        while (ch[j] != '\0') {
            AX_OLED_Disp16Char(x, y, ch[j], mode);
            x += 8;

            if (x > 120) {
                x = 0;
                y += 2;  // 当y>6时，超出部分不再显示
            }
            j++;
        }
    }
}

/**
 * @简  述  OLED指定位置显示一个数字（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          num：显示的数字
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Num(uint8_t x, uint8_t y, uint8_t num, uint8_t mode)  // 显示单个数字
{
    if (num < 10) {
        AX_OLED_Disp16Char(x, y, ('0' + num), mode);
    }
}

/**
 * @简  述  OLED指定位置显示数值（可显示负数）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          value：显示的数值，32位整型数据，最大显示10位（inte + deci < 10）
 *          inte：整数位数，范围（>0, inte + deci < 10）
 *          deci：小数位数, 范围（inte + deci < 10）（取0时，为整数）
 *          mode：显示模式，0-正常显示，1-反白显示
 * @说  明  输入value为整型，如果是小数，可扩大相应倍数实现。例如123.45,先乘100，显示时整数位取3，小数位取2。
 *          如果显示数据可能为负数时，整数位数需要比最大位数多一位。例如-122，整数位取4。
 * @返回值
 */
void AX_OLED_Disp16Value(uint8_t x, uint8_t y, int32_t value, uint8_t inte, uint8_t deci, uint8_t mode) {
    uint8_t dis_number[10];
    uint8_t i;
    uint32_t temp;

    if ((inte > 0) && ((inte + deci) < 10)) {
        if (value < 0)
            temp = (0 - value);
        else
            temp = value;

        dis_number[1] = temp % 10;
        dis_number[2] = (temp % 100) / 10;
        dis_number[3] = (temp % 1000) / 100;
        dis_number[4] = (temp % 10000) / 1000;
        dis_number[5] = (temp % 100000) / 10000;
        dis_number[6] = (temp % 1000000) / 100000;
        dis_number[7] = (temp % 10000000) / 1000000;
        dis_number[8] = (temp % 100000000) / 10000000;
        dis_number[9] = (temp % 1000000000) / 100000000;

        if (value < 0) {
            AX_OLED_Disp16Char(x, y, '-', 0);

            for (i = 0; i < (inte - 1); i++) {
                AX_OLED_Disp16Num(x + 8 + (i * 8), y, dis_number[inte + deci - i - 1], 0);
            }
            if (deci != 0) {
                AX_OLED_Disp16Char(x + (inte * 8), y, '.', 0);

                for (i = 0; i < deci; i++) {
                    AX_OLED_Disp16Num(x + (inte * 8) + 8 + (i * 8), y, dis_number[deci - i], 0);
                }
            }
        } else {
            for (i = 0; i < inte; i++) {
                AX_OLED_Disp16Num(x + (i * 8), y, dis_number[inte + deci - i], 0);
            }
            if (deci != 0) {
                AX_OLED_Disp16Char(x + (inte * 8), y, '.', 0);

                for (i = 0; i < deci; i++) {
                    AX_OLED_Disp16Num(x + (inte * 8) + 8 + (i * 8), y, dis_number[deci - i], 0);
                }
            }
        }
    } else  // 参数错误
    {
        // AX_OLED_DisplayChar(x, y, 'X', 0);
    }
}

/**
 * @简  述  OLED指定位置显示一个汉字（16X16）
 * @参  数  x：横坐标 0~112，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          pbuf：汉字编码数据指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispChinese(uint8_t x, uint8_t y, const uint8_t* pbuf, uint8_t mode) {
    uint8_t t;

    if ((x < 113) && (y < 7)) {
        if (mode) {
            OLED_Set_Pos(x, y);
            for (t = 0; t < 16; t++) {
                OLED_WriteData(~*pbuf++);
            }
            OLED_Set_Pos(x, y + 1);
            for (t = 0; t < 16; t++) {
                OLED_WriteData(~*pbuf++);
            }
        } else {
            OLED_Set_Pos(x, y);
            for (t = 0; t < 16; t++) {
                OLED_WriteData(*pbuf++);
            }
            OLED_Set_Pos(x, y + 1);
            for (t = 0; t < 16; t++) {
                OLED_WriteData(*pbuf++);
            }
        }
    }
}

/**
 * @简  述  OLED指定位置显示一个指定尺寸照片
 * @参  数  x：图片起始点横坐标，0~127
 *          y：图片起始点纵坐标，0~7
 *          xsize：X轴图片尺寸，1~128
 *          ysize：Y轴图片尺寸，1~8
 *          pbuf：照片数据指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispPicture(uint8_t x, uint8_t y, uint8_t xsize, uint8_t ysize, const uint8_t* pbuf, uint8_t mode) {
    uint8_t tx, ty;

    // 参数过滤
    if (x > 127 || y > 7)
        return;
    if ((x + xsize) > 128 || (y + ysize) > 8)
        return;

    if (mode) {
        for (ty = y; ty < ysize; ty++) {
            OLED_Set_Pos(x, ty);
            for (tx = x; tx < xsize; tx++) {
                OLED_WriteData(~*pbuf++);
            }
        }
    } else {
        for (ty = y; ty < ysize; ty++) {
            OLED_Set_Pos(x, ty);
            for (tx = x; tx < xsize; tx++) {
                OLED_WriteData(*pbuf++);
            }
        }
    }
}

/* 底层操作函数-----------------------------------------------------*/
/**
 * @简  述  SPI3写入读取一个字节函数
 * @参  数  dat：要写入的字节
 * @返回值  读出字节
 */
/* 底层操作函数-----------------------------------------------------*/
/**
 * @简  述  SPI1写入读取一个字节函数
 * @参  数  dat：要写入的字节
 * @返回值  读出字节
 */
static uint8_t SPI1_Send_Byte(uint8_t dat) {
    uint8_t rx_data;

    /* 使用HAL_SPI_TransmitReceive进行全双工SPI通信 */
    HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(hspi_ptr, &dat, &rx_data, 1, HAL_MAX_DELAY);

    /* 可选：检查传输状态 */
    if (status != HAL_OK) {
        // 处理错误，这里可以根据需要添加错误处理代码
        // 例如：Error_Handler();
    }

    /* 返回接收到的数据 */
    return rx_data;
}

/**
 * @brief
 * @param  None
 * @retval None
 */
static void OLED_WriteCmd(uint8_t data) {
    OLED_DC_L();

    SPI1_Send_Byte(data);  // 发送片擦除命令
    OLED_DC_H();
}

/**
 * @brief
 * @param  None
 * @retval None
 */
static void OLED_WriteData(uint8_t data) {
    SPI1_Send_Byte(data);  // 发送片擦除命令
}

static void OLED_Set_Pos(uint8_t x, uint8_t y) {
    OLED_WriteCmd(0xb0 + y);
    OLED_WriteCmd(((x & 0xf0) >> 4) | 0x10);
    OLED_WriteCmd((x & 0x0f) | 0x01);
}

/**
 * @brief  在OLED指定位置显示浮点数
 * @param  x：横坐标 0~122，超出范围不显示
 *         y：纵坐标 0~7，超出范围不显示
 *         value：显示的浮点数值
 *         inte：整数位数，范围（>0, inte + deci < 10）
 *         deci：小数位数，范围（inte + deci < 10）
 *         mode：显示模式，0-正常显示，1-反白显示
 * @note   整数位数需要包含可能的负号位置。
 *         例如-12.34，整数位应设为3（包含负号位置）。
 *         浮点数精度有限，建议小数位数不要超过6位。
 */
void AX_OLED_DispFloat(uint8_t x, uint8_t y, float value, uint8_t inte, uint8_t deci, uint8_t mode) {
    char buffer[12] = {0};
    uint8_t idx = 0;
    int32_t int_part, frac_part;
    uint32_t scale = 1;

    // 基本参数检查
    if (x > 122 || y > 7) return;

    // 计算缩放因子
    for (uint8_t i = 0; i < deci; i++) scale *= 10;

    // 处理符号和缩放
    if (value < 0) {
        buffer[idx++] = '-';
        value = -value;
    }
    int32_t scaled = (int32_t)(value * scale + 0.5f);

    // 分离整数和小数
    int_part = scaled / scale;
    frac_part = scaled % scale;

    // 显示整数部分
    if (int_part == 0) {
        buffer[idx++] = '0';
    } else {
        // 反转显示整数
        int32_t temp = int_part;
        int32_t divisor = 1;
        while (temp / divisor >= 10) divisor *= 10;

        while (divisor > 0) {
            buffer[idx++] = '0' + (temp / divisor);
            temp %= divisor;
            divisor /= 10;
        }
    }

    // 显示小数部分
    if (deci > 0) {
        buffer[idx++] = '.';

        // 显示小数，补前导零
        int32_t temp = frac_part;
        int32_t divisor = 1;
        for (uint8_t i = 1; i < deci; i++) divisor *= 10;

        while (divisor > 0) {
            buffer[idx++] = '0' + (temp / divisor);
            temp %= divisor;
            divisor /= 10;
        }
    }

    buffer[idx] = '\0';
    AX_OLED_DispStr(x, y, (uint8_t*)buffer, mode);
}

/**
 * @简  述  在OLED指定位置显示浮点数（自动计算整数位数）
 * @参  数  x：横坐标 0~122，超出范围不显示
 *         y：纵坐标 0~7，超出范围不显示
 *         value：显示的浮点数值
 *         deci：小数位数，范围（deci < 8）
 *         mode：显示模式，0-正常显示，1-反白显示
 * @返回值   自动计算所需的整数位数，最大支持8位整数
 */
void AX_OLED_DispFloatAuto(uint8_t x, uint8_t y, float value, uint8_t deci, uint8_t mode) {
    uint8_t inte;
    float abs_value = (value < 0) ? -value : value;

    // 自动计算整数位数
    if (abs_value < 10.0f) {
        inte = 1;  // 0-9
    } else if (abs_value < 100.0f) {
        inte = 2;  // 10-99
    } else if (abs_value < 1000.0f) {
        inte = 3;  // 100-999
    } else if (abs_value < 10000.0f) {
        inte = 4;  // 1000-9999
    } else if (abs_value < 100000.0f) {
        inte = 5;  // 10000-99999
    } else if (abs_value < 1000000.0f) {
        inte = 6;  // 100000-999999
    } else if (abs_value < 10000000.0f) {
        inte = 7;  // 1000000-9999999
    } else {
        inte = 8;  // 超出范围显示8位
    }

    // 如果为负数，整数位数需要加1来显示负号
    if (value < 0) {
        inte += 1;
    }

    // 检查总位数是否超出限制
    if (inte + deci >= 10) {
        // 如果超出，调整小数位数
        deci = 9 - inte;
        if (deci < 0) deci = 0;
    }

    AX_OLED_DispFloat(x, y, value, inte, deci, mode);
}

/******************* (C) 版权 2023 XTARK **************************************/
